/* -------------------------------------------------------------------------------

This code is based on source provided under the terms of the Id Software 
LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the
GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of 
LICENSE_ID, please contact Id Software immediately at info@idsoftware.com.

All changes and additions to the original source which have been developed by
other contributors (see CONTRIBUTORS) are provided under the terms of the
license the contributors choose (see LICENSE), to the extent permitted by the
LICENSE_ID. If you did not receive a copy of the contributor license,
please contact the GtkRadiant maintainers at info@gtkradiant.com immediately.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

----------------------------------------------------------------------------------

This code has been altered significantly from its original form, to support
several games based on the Quake III Arena engine, in the form of "Q3Map2."

------------------------------------------------------------------------------- */



/* marker */
#define LIGHT_BOUNCE_C



/* dependencies */
#include "q3map2.h"



/* functions */

/*
RadFreeLights()
deletes any existing lights, freeing up memory for the next bounce
*/

void RadFreeLights( void )
{
	light_t		*light, *next;
	
	
	/* delete lights */
	for( light = lights; light; light = next )
	{
		next = light->next;
		if( light->w != NULL )
			FreeWinding( light->w );
		free( light );
	}
	numLights = 0;
	lights = NULL;
}



/*
RadClipWindingEpsilon()
clips a rad winding by a plane
based off the regular clip winding code
*/

static void RadClipWindingEpsilon( radWinding_t *in, vec3_t normal, vec_t dist,
	vec_t epsilon, radWinding_t *front, radWinding_t *back, clipWork_t *cw )
{
	vec_t			*dists;
	int				*sides;
	int				counts[ 3 ];
	vec_t			dot;		/* ydnar: changed from static b/c of threading */ /* VC 4.2 optimizer bug if not static? */
	int				i, j, k;
	radVert_t		*v1, *v2, mid;
	int				maxPoints;
	
	
	/* crutch */
	dists = cw->dists;
	sides = cw->sides;
	
	/* clear counts */
	counts[ 0 ] = counts[ 1 ] = counts[ 2 ] = 0;

	/* determine sides for each point */
	for( i = 0; i < in->numVerts; i++ )
	{
		dot = DotProduct( in->verts[ i ].xyz, normal );
		dot -= dist;
		dists[ i ] = dot;
		if( dot > epsilon )
			sides[ i ] = SIDE_FRONT;
		else if( dot < -epsilon )
			sides[ i ] = SIDE_BACK;
		else
			sides[ i ] = SIDE_ON;
		counts[ sides[ i ] ]++;
	}
	sides[ i ] = sides[ 0 ];
	dists[ i ] = dists[ 0 ];
	
	/* clear front and back */
	front->numVerts = back->numVerts = 0;
	
	/* handle all on one side cases */
	if( counts[ 0 ] == 0 )
	{
		memcpy( back, in, sizeof( radWinding_t ) );
		return;
	}
	if( counts[ 1 ] == 0 )
	{
		memcpy( front, in, sizeof( radWinding_t ) );
		return;
	}
	
	/* setup windings */
	maxPoints = in->numVerts + 4;
	
	/* do individual verts */
	for( i = 0; i < in->numVerts; i++ )
	{
		/* do simple vertex copies first */
		v1 = &in->verts[ i ];
		
		if( sides[ i ] == SIDE_ON )
		{
			memcpy( &front->verts[ front->numVerts++ ], v1, sizeof( radVert_t ) );
			memcpy( &back->verts[ back->numVerts++ ], v1, sizeof( radVert_t ) );
			continue;
		}
	
		if( sides[ i ] == SIDE_FRONT )
			memcpy( &front->verts[ front->numVerts++ ], v1, sizeof( radVert_t ) );
		
		if( sides[ i ] == SIDE_BACK )
			memcpy( &back->verts[ back->numVerts++ ], v1, sizeof( radVert_t ) );
		
		if( sides[ i + 1 ] == SIDE_ON || sides[ i + 1 ] == sides[ i ] )
			continue;
			
		/* generate a split vertex */
		v2 = &in->verts[ (i + 1) % in->numVerts ];
		
		dot = dists[ i ] / (dists[ i ] - dists[ i + 1 ]);

		/* average vertex values */
		for( j = 0; j < 4; j++ )
		{
			/* color */
			if( j < 4 )
			{
				for( k = 0; k < MAX_LIGHTMAPS; k++ )
					mid.color[ k ][ j ] = v1->color[ k ][ j ] + dot * (v2->color[ k ][ j ] - v1->color[ k ][ j ]);
			}
			
			/* xyz, normal */
			if( j < 3 )
			{
				mid.xyz[ j ] = v1->xyz[ j ] + dot * (v2->xyz[ j ] - v1->xyz[ j ]);
				mid.normal[ j ] = v1->normal[ j ] + dot * (v2->normal[ j ] - v1->normal[ j ]);
			}
			
			/* st, lightmap */
			if( j < 2 )
			{
				mid.st[ j ] = v1->st[ j ] + dot * (v2->st[ j ] - v1->st[ j ]);
				for( k = 0; k < MAX_LIGHTMAPS; k++ )
					mid.lightmap[ k ][ j ] = v1->lightmap[ k ][ j ] + dot * (v2->lightmap[ k ][ j ] - v1->lightmap[ k ][ j ]);
			}
		}
		
		/* normalize the averaged normal */
		VectorNormalize( mid.normal, mid.normal );

		/* copy the midpoint to both windings */
		memcpy( &front->verts[ front->numVerts++ ], &mid, sizeof( radVert_t ) );
		memcpy( &back->verts[ back->numVerts++ ], &mid, sizeof( radVert_t ) );
	}
	
	/* error check */
	if( front->numVerts > maxPoints || front->numVerts > maxPoints )
		Error( "RadClipWindingEpsilon: points exceeded estimate" );
	if( front->numVerts > MAX_POINTS_ON_WINDING || front->numVerts > MAX_POINTS_ON_WINDING )
		Error( "RadClipWindingEpsilon: MAX_POINTS_ON_WINDING" );
}





/*
RadSampleImage()
samples a texture image for a given color
returns qfalse if pixels are bad
*/

qboolean RadSampleImage( byte *pixels, int width, int height, float st[ 2 ], float color[ 4 ] )
{
	float	sto[ 2 ];
	int		x, y;
	
	
	/* clear color first */
	color[ 0 ] = color[ 1 ] = color[ 2 ] = color[ 3 ] = 255;
	
	/* dummy check */
	if( pixels == NULL || width < 1 || height < 1 )
		return qfalse;
	
	/* bias st */
	sto[ 0 ] = st[ 0 ];
	while( sto[ 0 ] < 0.0f )
		sto[ 0 ] += 1.0f;
	sto[ 1 ] = st[ 1 ];
	while( sto[ 1 ] < 0.0f )
		sto[ 1 ] += 1.0f;

	/* get offsets */
	x = ((float) width * sto[ 0 ]) + 0.5f;
	x %= width;
	y = ((float) height * sto[ 1 ])  + 0.5f;
	y %= height;
	
	/* get pixel */
	pixels += (y * width * 4) + (x * 4);
	VectorCopy( pixels, color );
	color[ 3 ] = pixels[ 3 ];
	return qtrue;
}



/*
RadSample()
samples a fragment's lightmap or vertex color and returns an
average color and a color gradient for the sample
*/

#define MAX_SAMPLES			150
#define SAMPLE_GRANULARITY	6

static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm, shaderInfo_t *si, radWinding_t *rw, vec3_t average, vec3_t gradient, int *style )
{
	int			i, j, k, l, v, x, y, samples;
	vec3_t		color, mins, maxs;
	vec4_t		textureColor;
	float		alpha, alphaI, bf;
	vec3_t		blend;
	float		st[ 2 ], lightmap[ 2 ], *radLuxel;
	radVert_t	*rv[ 3 ];
	
	
	/* initial setup */
	ClearBounds( mins, maxs );
	VectorClear( average );
	VectorClear( gradient );
	alpha = 0;
	
	/* dummy check */
	if( rw == NULL || rw->numVerts < 3 )
		return;
	
	/* start sampling */
	samples = 0;
	
	/* sample vertex colors if no lightmap or this is the initial pass */
	if( lm == NULL || lm->radLuxels[ lightmapNum ] == NULL || bouncing == qfalse )
	{
		for( samples = 0; samples < rw->numVerts; samples++ )
		{
			/* multiply by texture color */
			if( !RadSampleImage( si->lightImage->pixels, si->lightImage->width, si->lightImage->height, rw->verts[ samples ].st, textureColor ) )
			{
				VectorCopy( si->averageColor, textureColor );
				textureColor[ 4 ] = 255.0f;
			}
			for( i = 0; i < 3; i++ )
				color[ i ] = (textureColor[ i ] / 255) * (rw->verts[ samples ].color[ lightmapNum ][ i ] / 255.0f);
			
			AddPointToBounds( color, mins, maxs );
			VectorAdd( average, color, average );
			
			/* get alpha */
			alpha += (textureColor[ 3 ] / 255.0f) * (rw->verts[ samples ].color[ lightmapNum ][ 3 ] / 255.0f);
		}
		
		/* set style */
		*style = ds->vertexStyles[ lightmapNum ];
	}
	
	/* sample lightmap */
	else
	{
		/* fracture the winding into a fan (including degenerate tris) */
		for( v = 1; v < (rw->numVerts - 1) && samples < MAX_SAMPLES; v++ )
		{
			/* get a triangle */
			rv[ 0 ] = &rw->verts[ 0 ];
			rv[ 1 ] = &rw->verts[ v ];
			rv[ 2 ] = &rw->verts[ v + 1 ];
			
			/* this code is embarassing (really should just rasterize the triangle) */
			for( i = 1; i < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; i++ )
			{
				for( j = 1; j < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; j++ )
				{
					for( k = 1; k < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; k++ )
					{
						/* create a blend vector (barycentric coordinates) */
						blend[ 0 ] = i;
						blend[ 1 ] = j;
						blend[ 2 ] = k;
						bf = (1.0 / (blend[ 0 ] + blend[ 1 ] + blend[ 2 ]));
						VectorScale( blend, bf, blend );
						
						/* create a blended sample */
						st[ 0 ] = st[ 1 ] = 0.0f;
						lightmap[ 0 ] = lightmap[ 1 ] = 0.0f;
						alphaI = 0.0f;
						for( l = 0; l < 3; l++ )
						{
							st[ 0 ] += (rv[ l ]->st[ 0 ] * blend[ l ]);
							st[ 1 ] += (rv[ l ]->st[ 1 ] * blend[ l ]);
							lightmap[ 0 ] += (rv[ l ]->lightmap[ lightmapNum ][ 0 ] * blend[ l ]);
							lightmap[ 1 ] += (rv[ l ]->lightmap[ lightmapNum ][ 1 ] * blend[ l ]);
							alphaI += (rv[ l ]->color[ lightmapNum ][ 3 ] * blend[ l ]);
						}
						
						/* get lightmap xy coords */
						x = lightmap[ 0 ] / (float) superSample;
						y = lightmap[ 1 ] / (float) superSample;
						if( x < 0 )
							x = 0;
						else if ( x >= lm->w )
							x = lm->w - 1;
						if( y < 0 )
							y = 0;
						else if ( y >= lm->h )
							y = lm->h - 1;
						
						/* get radiosity luxel */
						radLuxel = RAD_LUXEL( lightmapNum, x, y );
						
						/* ignore unlit/unused luxels */
						if( radLuxel[ 0 ] < 0.0f )
							continue;
						
						/* inc samples */
						samples++;
						
						/* multiply by texture color */
						if( !RadSampleImage( si->lightImage->pixels, si->lightImage->width, si->lightImage->height, st, textureColor ) )
						{
							VectorCopy( si->averageColor, textureColor );
							textureColor[ 4 ] = 255;
						}
						for( i = 0; i < 3; i++ )
							color[ i ] = (textureColor[ i ] / 255) * (radLuxel[ i ] / 255);
						
						AddPointToBounds( color, mins, maxs );
						VectorAdd( average, color, average );
						
						/* get alpha */
						alpha += (textureColor[ 3 ] / 255) * (alphaI / 255);
					}
				}
			}
		}
		
		/* set style */
		*style = ds->lightmapStyles[ lightmapNum ];
	}
	
	/* any samples? */
	if( samples <= 0 )
		return;
	
	/* average the color */
	VectorScale( average, (1.0 / samples), average );
	
	/* create the color gradient */
	//%	VectorSubtract( maxs, mins, delta );
	
	/* new: color gradient will always be 0-1.0, expressed as the range of light relative to overall light */
	//%	gradient[ 0 ] = maxs[ 0 ] > 0.0f ? (maxs[ 0 ] - mins[ 0 ]) / maxs[ 0 ] : 0.0f;
	//%	gradient[ 1 ] = maxs[ 1 ] > 0.0f ? (maxs[ 1 ] - mins[ 1 ]) / maxs[ 1 ] : 0.0f;
	//%	gradient[ 2 ] = maxs[ 2 ] > 0.0f ? (maxs[ 2 ] - mins[ 2 ]) / maxs[ 2 ] : 0.0f;
	
	/* newer: another contrast function */
	for( i = 0; i < 3; i++ )
		gradient[ i ] = (maxs[ i ] - mins[ i ]) * maxs[ i ];
}



/*
RadSubdivideDiffuseLight()
subdivides a radiosity winding until it is smaller than subdivide, then generates an area light
*/

#define RADIOSITY_MAX_GRADIENT		0.75f	//%	0.25f
#define RADIOSITY_VALUE				500.0f
#define RADIOSITY_MIN				0.0001f
#define RADIOSITY_CLIP_EPSILON		0.125f

static void RadSubdivideDiffuseLight( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm, shaderInfo_t *si,
	float scale, float subdivide, qboolean original, radWinding_t *rw, clipWork_t *cw )
{
	int				i, style;
	float			dist, area, value;
	vec3_t			mins, maxs, normal, d1, d2, cross, color, gradient;
	light_t			*light, *splash;
	winding_t		*w;
	
	
	/* dummy check */
	if( rw == NULL || rw->numVerts < 3 )
		return;
	
	/* get bounds for winding */
	ClearBounds( mins, maxs );
	for( i = 0; i < rw->numVerts; i++ )
		AddPointToBounds( rw->verts[ i ].xyz, mins, maxs );
	
	/* subdivide if necessary */
	for( i = 0; i < 3; i++ )
	{
		if( maxs[ i ] - mins[ i ] > subdivide )
		{
			radWinding_t	front, back;
			
			
			/* make axial plane */
			VectorClear( normal );
			normal[ i ] = 1;
			dist = (maxs[ i ] + mins[ i ]) * 0.5f;
			
			/* clip the winding */
			RadClipWindingEpsilon( rw, normal, dist, RADIOSITY_CLIP_EPSILON, &front, &back, cw );
			
			/* recurse */
			RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qfalse, &front, cw );
			RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qfalse, &back, cw );
			return;
		}
	}
	
	/* check area */
	area = 0.0f;
	for( i = 2; i < rw->numVerts; i++ )
	{
		VectorSubtract( rw->verts[ i - 1 ].xyz, rw->verts[ 0 ].xyz, d1 );
		VectorSubtract( rw->verts[ i ].xyz, rw->verts[ 0 ].xyz, d2 );
		CrossProduct( d1, d2, cross );
		area += 0.5f * VectorLength( cross );
	}
	if( area < 1.0f || area > 20000000.0f )
		return;
	
	/* more subdivision may be necessary */
	if( bouncing )
	{
		/* get color sample for the surface fragment */
		RadSample( lightmapNum, ds, lm, si, rw, color, gradient, &style );
		
		/* if color gradient is too high, subdivide again */
		if( subdivide > minDiffuseSubdivide && 
			(gradient[ 0 ] > RADIOSITY_MAX_GRADIENT || gradient[ 1 ] > RADIOSITY_MAX_GRADIENT || gradient[ 2 ] > RADIOSITY_MAX_GRADIENT) )
		{
			RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, (subdivide / 2.0f), qfalse, rw, cw );
			return;
		}
	}
	
	/* create a regular winding and an average normal */
	w = AllocWinding( rw->numVerts );
	w->numpoints = rw->numVerts;
	VectorClear( normal );
	for( i = 0; i < rw->numVerts; i++ )
	{
		VectorCopy( rw->verts[ i ].xyz, w->p[ i ] );
		VectorAdd( normal, rw->verts[ i ].normal, normal );
	}
	VectorScale( normal, (1.0f / rw->numVerts), normal );
	if( VectorNormalize( normal, normal ) == 0.0f )
		return;
	
	/* early out? */
	if( bouncing && VectorLength( color ) < RADIOSITY_MIN )
		return;
	
	/* debug code */
	//%	Sys_Printf( "Size: %d %d %d\n", (int) (maxs[ 0 ] - mins[ 0 ]), (int) (maxs[ 1 ] - mins[ 1 ]), (int) (maxs[ 2 ] - mins[ 2 ]) );
	//%	Sys_Printf( "Grad: %f %f %f\n", gradient[ 0 ], gradient[ 1 ], gradient[ 2 ] );
	
	/* increment counts */
	numDiffuseLights++;
	switch( ds->surfaceType )
	{
		case MST_PLANAR:
			numBrushDiffuseLights++;
			break;
		
		case MST_TRIANGLE_SOUP:
			numTriangleDiffuseLights++;
			break;
		
		case MST_PATCH:
			numPatchDiffuseLights++;
			break;
	}
	
	/* create a light */
	light = safe_malloc( sizeof( *light ) );
	memset( light, 0, sizeof( *light ) );
	
	/* attach it */
	ThreadLock();
	light->next = lights;
	lights = light;
	ThreadUnlock();
	
	/* initialize the light */
	light->flags = LIGHT_AREA_DEFAULT;
	light->type = EMIT_AREA;
	light->si = si;
	light->fade = 1.0f;
	light->w = w;
	
	/* set falloff threshold */
	light->falloffTolerance = falloffTolerance;
	
	/* bouncing light? */
	if( bouncing == qfalse )
	{
		/* handle first-pass lights in normal q3a style */
		value = si->value;
		light->photons = value * area * areaScale;
		light->add = value * formFactorValueScale * areaScale;
		VectorCopy( si->color, light->color );
		VectorScale( light->color, light->add, light->emitColor );
		light->style = si->lightStyle;
		if( light->style < 0 || light->style >= LS_NONE )
			light->style = 0;
		
		/* set origin */
		VectorAdd( mins, maxs, light->origin );
		VectorScale( light->origin, 0.5f, light->origin );
		
		/* nudge it off the plane a bit */
		VectorCopy( normal, light->normal );
		VectorMA( light->origin, 1.0f, light->normal, light->origin );
		light->dist = DotProduct( light->origin, normal );
		
		/* optionally create a point splashsplash light for first pass */
		if( original && si->backsplashFraction > 0 )
		{
			/* allocate a new point light */
			splash = safe_malloc( sizeof( *splash ) );
			memset( splash, 0, sizeof( *splash ) );
			splash->next = lights;
			lights = splash;
			
			/* set it up */
			splash->flags = LIGHT_Q3A_DEFAULT;
			splash->type = EMIT_POINT;
			splash->photons = light->photons * si->backsplashFraction;
			splash->fade = 1.0f;
			splash->si = si;
			VectorMA( light->origin, si->backsplashDistance, normal, splash->origin );
			VectorCopy( si->color, splash->color );
			splash->falloffTolerance = falloffTolerance;
			splash->style = light->style;
			
			/* add to counts */
			numPointLights++;
		}
	}
	else
	{
		/* handle bounced light (radiosity) a little differently */
		value = RADIOSITY_VALUE * si->bounceScale * 0.375f;
		light->photons = value * area * bounceScale;
		light->add = value * formFactorValueScale * bounceScale;
		VectorCopy( color, light->color );
		VectorScale( light->color, light->add, light->emitColor );
		light->style = style;
		if( light->style < 0 || light->style >= LS_NONE )
			light->style = 0;
		
		/* set origin */
		WindingCenter( w, light->origin );
		
		/* nudge it off the plane a bit */
		VectorCopy( normal, light->normal );
		VectorMA( light->origin, 1.0f, light->normal, light->origin );
		light->dist = DotProduct( light->origin, normal );
	}
	
	/* emit light from both sides? */
	if( si->compileFlags & C_FOG || si->twoSided )
		light->flags |= LIGHT_TWOSIDED;
	
	//%	Sys_Printf( "\nAL: C: (%6f, %6f, %6f) [%6f] N: (%6f, %6f, %6f) %s\n",
	//%		light->color[ 0 ], light->color[ 1 ], light->color[ 2 ], light->add,
	//%		light->normal[ 0 ], light->normal[ 1 ], light->normal[ 2 ],
	//%		light->si->shader );
}



/*
RadLightForTriangles()
creates unbounced diffuse lights for triangle soup (misc_models, etc)
*/

void RadLightForTriangles( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t *si, float scale, float subdivide, clipWork_t *cw )
{
	int					i, j, k, v;
	bspDrawSurface_t	*ds;
	surfaceInfo_t		*info;
	float				*radVertexLuxel;
	radWinding_t		rw;
	
	
	/* get surface */
	ds = &bspDrawSurfaces[ num ];
	info = &surfaceInfos[ num ];
	
	/* each triangle is a potential emitter */
	rw.numVerts = 3;
	for( i = 0; i < ds->numIndexes; i += 3 )
	{
		/* copy each vert */
		for( j = 0; j < 3; j++ )
		{
			/* get vertex index and rad vertex luxel */
			v = ds->firstVert + bspDrawIndexes[ ds->firstIndex + i + j ];
			
			/* get most everything */
			memcpy( &rw.verts[ j ], &yDrawVerts[ v ], sizeof( bspDrawVert_t ) );
			
			/* fix colors */
			for( k = 0; k < MAX_LIGHTMAPS; k++ )
			{
				radVertexLuxel = RAD_VERTEX_LUXEL( k, ds->firstVert + bspDrawIndexes[ ds->firstIndex + i + j ] );
				VectorCopy( radVertexLuxel, rw.verts[ j ].color[ k ] );
				rw.verts[ j ].color[ k ][ 3 ] = yDrawVerts[ v ].color[ k ][ 3 ];
			} 
		}
		
		/* subdivide into area lights */
		RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw );
	}
}



/*
RadLightForPatch()
creates unbounced diffuse lights for patches
*/

#define	PLANAR_EPSILON	0.1f

void RadLightForPatch( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t *si, float scale, float subdivide, clipWork_t *cw )
{
	int					i, x, y, v, t, pw[ 5 ], r;
	bspDrawSurface_t	*ds;
	surfaceInfo_t		*info;
	bspDrawVert_t		*bogus;
	bspDrawVert_t		*dv[ 4 ];
	mesh_t				src, *subdivided, *mesh;
	float				*radVertexLuxel;
	float				dist;
	vec4_t				plane;
	qboolean			planar;
	radWinding_t		rw;
	
	
	/* get surface */
	ds = &bspDrawSurfaces[ num ];
	info = &surfaceInfos[ num ];
	
	/* construct a bogus vert list with color index stuffed into color[ 0 ] */
	bogus = safe_malloc( ds->numVerts * sizeof( bspDrawVert_t ) );
	memcpy( bogus, &yDrawVerts[ ds->firstVert ], ds->numVerts * sizeof( bspDrawVert_t ) );
	for( i = 0; i < ds->numVerts; i++ )
		bogus[ i ].color[ 0 ][ 0 ] = i;
	
	/* build a subdivided mesh identical to shadow facets for this patch */
	/* this MUST MATCH FacetsForPatch() identically! */
	src.width = ds->patchWidth;
	src.height = ds->patchHeight;
	src.verts = bogus;
	//%	subdivided = SubdivideMesh( src, 8, 512 );
	subdivided = SubdivideMesh2( src, info->patchIterations );
	PutMeshOnCurve( *subdivided );
	//%	MakeMeshNormals( *subdivided );
	mesh = RemoveLinearMeshColumnsRows( subdivided );
	FreeMesh( subdivided );
	free( bogus );
	
	/* FIXME: build interpolation table into color[ 1 ] */
	
	/* fix up color indexes */
	for( i = 0; i < (mesh->width * mesh->height); i++ )
	{
		dv[ 0 ] = &mesh->verts[ i ];
		if( dv[ 0 ]->color[ 0 ][ 0 ] >= ds->numVerts )
			dv[ 0 ]->color[ 0 ][ 0 ] = ds->numVerts - 1;
	}
	
	/* iterate through the mesh quads */
	for( y = 0; y < (mesh->height - 1); y++ )
	{
		for( x = 0; x < (mesh->width - 1); x++ )
		{
			/* set indexes */
			pw[ 0 ] = x + (y * mesh->width);
			pw[ 1 ] = x + ((y + 1) * mesh->width);
			pw[ 2 ] = x + 1 + ((y + 1) * mesh->width);
			pw[ 3 ] = x + 1 + (y * mesh->width);
			pw[ 4 ] = x + (y * mesh->width);	/* same as pw[ 0 ] */
			
			/* set radix */
			r = (x + y) & 1;
			
			/* get drawverts */
			dv[ 0 ] = &mesh->verts[ pw[ r + 0 ] ];
			dv[ 1 ] = &mesh->verts[ pw[ r + 1 ] ];
			dv[ 2 ] = &mesh->verts[ pw[ r + 2 ] ];
			dv[ 3 ] = &mesh->verts[ pw[ r + 3 ] ];
			
			/* planar? */
			planar = PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz );
			if( planar )
			{
				dist = DotProduct( dv[ 1 ]->xyz, plane ) - plane[ 3 ];
				if( fabs( dist ) > PLANAR_EPSILON )
					planar = qfalse;
			}
			
			/* generate a quad */
			if( planar )
			{
				rw.numVerts = 4;
				for( v = 0; v < 4; v++ )
				{
					/* get most everything */
					memcpy( &rw.verts[ v ], dv[ v ], sizeof( bspDrawVert_t ) );
					
					/* fix colors */
					for( i = 0; i < MAX_LIGHTMAPS; i++ )
					{
						radVertexLuxel = RAD_VERTEX_LUXEL( i, ds->firstVert + dv[ v ]->color[ 0 ][ 0 ] );
						VectorCopy( radVertexLuxel, rw.verts[ v ].color[ i ] );
						rw.verts[ v ].color[ i ][ 3 ] = dv[ v ]->color[ i ][ 3 ];
					}
				}
				
				/* subdivide into area lights */
				RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw );
			}
			
			/* generate 2 tris */
			else
			{
				rw.numVerts = 3;
				for( t = 0; t < 2; t++ )
				{
					for( v = 0; v < 3 + t; v++ )
					{
						/* get "other" triangle (stupid hacky logic, but whatevah) */
						if( v == 1 && t == 1 )
							v++;

						/* get most everything */
						memcpy( &rw.verts[ v ], dv[ v ], sizeof( bspDrawVert_t ) );
						
						/* fix colors */
						for( i = 0; i < MAX_LIGHTMAPS; i++ )
						{
							radVertexLuxel = RAD_VERTEX_LUXEL( i, ds->firstVert + dv[ v ]->color[ 0 ][ 0 ] );
							VectorCopy( radVertexLuxel, rw.verts[ v ].color[ i ] );
							rw.verts[ v ].color[ i ][ 3 ] = dv[ v ]->color[ i ][ 3 ];
						}
					}
					
					/* subdivide into area lights */
					RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw );
				}
			}
		}
	}
	
	/* free the mesh */
	FreeMesh( mesh );
}




/*
RadLight()
creates unbounced diffuse lights for a given surface
*/

void RadLight( int num )
{
	int					lightmapNum;
	float				scale, subdivide;
	int					contentFlags, surfaceFlags, compileFlags;
	bspDrawSurface_t	*ds;
	surfaceInfo_t		*info;
	rawLightmap_t		*lm;
	shaderInfo_t		*si;
	clipWork_t			cw;
	
	
	/* get drawsurface, lightmap, and shader info */
	ds = &bspDrawSurfaces[ num ];
	info = &surfaceInfos[ num ];
	lm = info->lm;
	si = info->si;
	scale = si->bounceScale;
	
	/* find nodraw bit */
	contentFlags = surfaceFlags = compileFlags = 0;
	ApplySurfaceParm( "nodraw", &contentFlags, &surfaceFlags, &compileFlags );
	
	/* early outs? */
	if( scale <= 0.0f || (si->compileFlags & C_SKY) || si->autosprite ||
		(bspShaders[ ds->shaderNum ].contentFlags & contentFlags) || (bspShaders[ ds->shaderNum ].surfaceFlags & surfaceFlags) ||
		(si->compileFlags & compileFlags) )
		return;
	
	/* determine how much we need to chop up the surface */
	if( si->lightSubdivide )
		subdivide = si->lightSubdivide;
	else
		subdivide = diffuseSubdivide;
	
	/* inc counts */
	numDiffuseSurfaces++;
	
	/* iterate through styles (this could be more efficient, yes) */
	for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ )
	{
		/* switch on type */
		if( ds->lightmapStyles[ lightmapNum ] != LS_NONE && ds->lightmapStyles[ lightmapNum ] != LS_UNUSED )
		{
			switch( ds->surfaceType )
			{
				case MST_PLANAR:
				case MST_TRIANGLE_SOUP:
					RadLightForTriangles( num, lightmapNum, lm, si, scale, subdivide, &cw );
					break;
				
				case MST_PATCH:
					RadLightForPatch( num, lightmapNum, lm, si, scale, subdivide, &cw );
					break;
				
				default:
					break;
			}
		}
	}
}



/*
RadCreateDiffuseLights()
creates lights for unbounced light on surfaces in the bsp
*/

int	iterations = 0;

void RadCreateDiffuseLights( void )
{
	/* startup */
	Sys_FPrintf( SYS_VRB, "--- RadCreateDiffuseLights ---\n" );
	numDiffuseSurfaces = 0;
	numDiffuseLights = 0;
	numBrushDiffuseLights = 0;
	numTriangleDiffuseLights = 0;
	numPatchDiffuseLights = 0;
	numAreaLights = 0;
	
	/* hit every surface (threaded) */
	RunThreadsOnIndividual( numBSPDrawSurfaces, qtrue, RadLight );
	
	/* dump the lights generated to a file */
	if( dump )
	{
		char	dumpName[ 1024 ], ext[ 64 ];
		FILE	*file;
		light_t	*light;
		
		strcpy( dumpName, source );
		StripExtension( dumpName );
		sprintf( ext, "_bounce_%03d.map", iterations );
		strcat( dumpName, ext );
		file = fopen( dumpName, "wb" );
		Sys_Printf( "Writing %s...\n", dumpName );
		if( file )
		{
			for( light = lights; light; light = light->next )
			{
				fprintf( file,
					"{\n"
					"\"classname\" \"light\"\n"
					"\"light\" \"%d\"\n"
					"\"origin\" \"%.0f %.0f %.0f\"\n"
					"\"_color\" \"%.3f %.3f %.3f\"\n"
					"}\n",
					
					(int) light->add,
					
					light->origin[ 0 ],
					light->origin[ 1 ],
					light->origin[ 2 ],
					
					light->color[ 0 ],
					light->color[ 1 ],
					light->color[ 2 ] );
			}
			fclose( file );
		}
	}
	
	/* increment */
	iterations++;
	
	/* print counts */
	Sys_Printf( "%8d diffuse surfaces\n", numDiffuseSurfaces );
	Sys_FPrintf( SYS_VRB, "%8d total diffuse lights\n", numDiffuseLights );
	Sys_FPrintf( SYS_VRB, "%8d brush diffuse lights\n", numBrushDiffuseLights );
	Sys_FPrintf( SYS_VRB, "%8d patch diffuse lights\n", numPatchDiffuseLights );
	Sys_FPrintf( SYS_VRB, "%8d triangle diffuse lights\n", numTriangleDiffuseLights );
}





